import {extend} from "../shared";
import {reactive} from "./reactive";

let activeEffect;
let shouldTrack;

export class ReactiveEffect {
    private _fn: any;
    deps = []
    active = true
    onStop?: () => void

    constructor(fn, public scheduler?) {
        this._fn = fn
    }

    run() {
        if (!this.active) {
            return this._fn()
        }
        shouldTrack = true;
        activeEffect = this;
        const result = this._fn()
        shouldTrack = false;
        return result;
    }

    stop() {
        if (this.active) {
            cleanupEffect(this);
            if (this.onStop) {
                this.onStop();
            }
            this.active = false;
        }
    }
}

function cleanupEffect(effect) {
    effect.deps.forEach((dep: any) => {
        dep.delete(effect);
    })
    effect.deps.length = 0;
}

export function effect(fn, options: any = {}) {
    const scheduler = options.scheduler;
    const _effect = new ReactiveEffect(fn, options.scheduler);
    // _effect.onStop = options.onStop;
    // Object.assign(_effect, options);
    // 扩展_effect 对象的属性
    extend(_effect, options);

    _effect.run()

    let runner: any = _effect.run.bind(_effect);
    runner.effect = _effect;
    return runner;
}

//  对象和它的key对应的依赖。
const targetMap = new Map();

export function trackEffect(dep) {
    //  如果之前已经添加过 activeEffect 了，那么就直接return，不在继续添加了
    if (dep.has(activeEffect)) return;
    //  把activeEffect 添加到key对应的依赖中去
    dep.add(activeEffect);
    // activeEffect 对象身上把dep反向加到自己属性中去，待会stop的时候可以使用，把自己从dep中删除
    activeEffect.deps.push(dep);
}

export function track(target: any, key: string | symbol) {
    // activeEffect 是 undefined的时候，肯定是没走进effect 函数内部，所以也不用去做依赖收集
    // if (!activeEffect) return;
    // //  不应该收集那我就直接返回了。
    // if (!shouldTrack) return;

    if (!isTracking()) return;
    // 取出key和它对应的依赖Map
    let depsMap = targetMap.get(target);
    // 如果没有值，那么就创建一个Map，然后set进去。
    if (!depsMap) {
        depsMap = new Map();
        targetMap.set(target, depsMap);
    }
    //  取出key对应的依赖，如果没有，那么就创建一个Set，然后set进去。
    let dep = depsMap.get(key);
    if (!dep) {
        dep = new Set();
        depsMap.set(key, dep)
    }
    //  抽离出来方便 ref 复用
    trackEffect(dep);
}

/**
 *  是否需要做依赖收集，如果在effect函数内部触发了get 操作，
 *  此时activeEffect是undefined，是不该触发依赖收集操作的。
 */
export function isTracking() {
    return shouldTrack && activeEffect !== undefined;
    // activeEffect 是 undefined的时候，肯定是没走进effect 函数内部，所以也不用去做依赖收集
    // if (!activeEffect) return;
    // //  不应该收集那我就直接返回了。
    // if (!shouldTrack) return;
}


export function triggerEffct(dep) {
    for (const effect of dep) {
        if (effect) {
            if (effect.scheduler) {
                effect.scheduler()
            } else {
                effect.run()
            }
        }
    }
}

export function trigger(target, key) {
    // 取出对象对应的依赖Map.
    let depsMap = targetMap.get(target);
    //  取出key对应的 effect
    let dep = depsMap.get(key);
    // 挨个重复执行。
    // 为了ref 复用，抽一下这个方法。
    triggerEffct(dep);
}

export function stop(runner) {
    runner.effect.stop()
}
